/**
 * 实现一个babel插件
 * 把es6 的箭头函数 变成es5的普通函数
 * cnpm i @babel/core  babel-types  babel-plugin-transform-es2015-arrow-functions -D
 */
// 实现 babel 插件的时候有一些原则 1.尽可能复用老节点 2.尽可能少操作

const core = require("@babel/core"); //核心库
let types = require("babel-types"); //生成新节点 

//访问者visitor 迭代器模式 iterator for(let key of obj){}
let babelPluginTransformEs2015ArrowFunctions = {
    visitor : {
        // 属性key就是我这个插件关心或者说要处理的节点的类型
        // visitor key 表示的 ast 中的 type 
        ArrowFunctionExpression(nodePath){
            let node = nodePath.node;
            hoistFunctionEnvironment(nodePath);//提升函数运行环境
            node.type = "FunctionExpression";
        }
    }
}

function hoistFunctionEnvironment(fnPath){
    // fnPath 有 parentPath
    fnPath.findParent = function (judge) {
        let parentPath = this.parentPath;
        while (parentPath) {
          if (judge(parentPath)) {
            return parentPath;
          } else {
            parentPath = parentPath.parentPath;
          }
        }
    };

    const thisEnvFn = fnPath.findParent((p) => { //向上查找
        //return (types.isFunction(p.node) && !types.isArrowFunctionExprssion(p.node)) || types.isProgram(p.node);
        return (p.isFunction() && !p.isArrowFunctionExprssion()) || p.isProgram(); //如果是普通函数或者是根节点
    });
    let thisPaths = getScopeInfomation(fnPath); 

    if (thisPaths.length > 0) {
        let thisBinding = "_this";
        //types 1.用来判断某个节点是不是某个类型 2 用来创建一个新的节点
        let _thisIdentifier = types.identifier(thisBinding);
        let _thisVariableDeclarator = types.variableDeclarator(
          _thisIdentifier,
          types.thisExpression()
        );
        thisEnvFn.scope.push(_thisVariableDeclarator);
        thisPaths.forEach((thisPath) => {
          thisPath.replaceWith(_thisIdentifier);
        });
    }
}

// 获取当前作用域内this信息
function getScopeInfomation(fnPath){
    let thisPaths = [];
    // 获取type = ThisExpression的节点信息
    fnPath.traverse({
        ThisExpression(thisPath) {
          thisPaths.push(thisPath);
        },
    });
    return thisPaths;
}


let sourceCode = `
 const sum = (a,b)=>{
    console.log(this);
    console.log(this);
     return a+b;
 }
`;
let {code} = core.transform(sourceCode,{
    plugins:[babelPluginTransformEs2015ArrowFunctions]
})
console.log(code)
// ===>
// const sum = function (a, b) {
//     return a + b;
// };
